package com.demo.pong.service.impl

import com.demo.pong.exception.CustomException
import com.demo.pong.orm.entity.ErrorLogs
import com.demo.pong.service.ErrorLogsService
import com.mongodb.client.MongoCollection
import com.mongodb.client.MongoDatabase
import org.bson.Document
import org.springframework.http.HttpStatus
import org.springframework.web.server.ResponseStatusException
import spock.lang.Specification
import com.mongodb.client.FindIterable
import spock.lang.Subject

class PongServiceImplTest extends Specification {

    def errorLogsService = Mock(ErrorLogsService)
    def mongoDatabase = Mock(MongoDatabase)
    def mongoCollection = Mock(MongoCollection)

    @Subject
    def pongService = new PongServiceImpl(errorLogsService, mongoDatabase)

    def setup() {
        mongoDatabase.getCollection("locks") >> mongoCollection
    }

    def "should return 'World' when message is 'hello' and lock is acquired"() {
        when:
        def result = pongService.respond("hello", "traceId").block()

        then:
        result == "World"
    }

    def "should throw CustomException when message is blank"() {
        when:
        pongService.respond("", "traceId").block()

        then:
        def e = thrown(CustomException)
        e.message == "msg is required"
    }

    def "should throw ResponseStatusException when message is not 'hello'"() {
        when:
        pongService.respond("invalidMessage", "traceId").block()

        then:
        def e = thrown(ResponseStatusException)
        e.getStatusCode() == HttpStatus.BAD_REQUEST
    }

    def "should return 429 when lock is not acquired"() {
        setup:
        // An exception is thrown when a lock is inserted
        mongoCollection.insertOne(_) >> { throw new Exception("Lock already exists") }
        // Simulating a lock lookup returns an existing lock document
         def document = new Document("_id", "someLockId")
        document.put("timestamp", new Date())
        def findResult = Mock(FindIterable)
        findResult.first() >> document
        mongoCollection.find(new Document("_id", PongServiceImpl.LOCK_ID)) >> findResult

        when:
        def result = pongService.respond("hello", "traceId").block()

        then:
        def e = thrown(ResponseStatusException)
        e.getStatusCode() == HttpStatus.TOO_MANY_REQUESTS
        // make sure the number of calls is 1
        1 * errorLogsService.saveErrorLog(_ as ErrorLogs)
    }

    def "should release lock after processing"() {
        setup:
        mongoCollection.insertOne(_) >> { }
        mongoCollection.find(_) >> null // there are no existing locks

        when:
        pongService.respond("hello", "traceId").block()

        then:
        1 * mongoCollection.deleteOne(new Document("_id", "pongServiceLock"))
    }

    def "should acquire lock when existing lock has expired"() {
        setup:
        // An exception is thrown when a lock is inserted
        mongoCollection.insertOne(_) >> { throw new Exception("Lock already exists") }
        // Simulating a lock lookup returns an existing lock document
         def document = new Document("_id", "someLockId")
        Date now = new Date()

        Calendar calendar = Calendar.getInstance()
        calendar.setTime(now)

        // Add -2 seconds to the current time
        calendar.add(Calendar.SECOND, -2)

        // Get the modified time
        Date newDate = calendar.getTime()
        document.put("timestamp", newDate)
        def findResult = Mock(FindIterable)
        findResult.first() >> document
        mongoCollection.find(new Document("_id", "pongServiceLock")) >> findResult

        when:
        def result = pongService.respond("hello", "traceId").block()

        then:
        result == "World"
    }
}